feat: implement should_apply_proposer_boost for gloas - #9233
Conversation
Implement the `should_apply_proposer_boost` logic from consensus-specs commit 71d1151 (PR #4807). This addresses the builder reveal safety concern where a colluding next-slot proposer could use proposer boost to override a legitimately revealed block. Changes: - Add `ptcTimeliness` and `proposerIndex` fields to ProtoBlock - Add `isBlockPtcTimely` to track PTC deadline timeliness - Add `shouldApplyProposerBoost` which withholds boost when the parent is a weak, equivocating block from the previous slot - Add `findEquivocatingBlocks` in ProtoArray to detect proposer equivocations by scanning for PTC-timely blocks at the same slot from the same proposer - Gate proposer boost in `getWeight` on `shouldApplyProposerBoost()` - Pre-gloas blocks retain unconditional boost (backward compatible) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request implements the Gloas fork-choice logic for proposer boost and PTC (Payload Timeliness Committee) timeliness. It adds ptcTimeliness and proposerIndex to block metadata and introduces the shouldApplyProposerBoost logic, which considers parent block weight and proposer equivocations. Feedback focuses on preventing a crash during the fork transition by using dynamic payload status for parent nodes and optimizing the performance of the equivocation check to avoid O(N) map iterations during head updates.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9bc6eb6ce5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
| Benchmark suite | Current: 774d3e6 | Previous: 9572bcc | Ratio |
|---|---|---|---|
| send data - 1000 4096B messages | 54.297 ms/op | 17.009 ms/op | 3.19 |
Full benchmark results
| Benchmark suite | Current: 774d3e6 | Previous: 9572bcc | Ratio |
|---|---|---|---|
| getPubkeys - native cache - req 1000 vs - 250000 vc | 418.77 us/op | 410.03 us/op | 1.02 |
| getPubkeys - validatorsArr - req 1000 vs - 250000 vc | 40.655 us/op | 39.409 us/op | 1.03 |
| BLS verify - blst | 931.35 us/op | 885.39 us/op | 1.05 |
| BLS verifyMultipleSignatures 3 - blst | 1.4029 ms/op | 1.3878 ms/op | 1.01 |
| BLS verifyMultipleSignatures 8 - blst | 2.3683 ms/op | 2.2394 ms/op | 1.06 |
| BLS verifyMultipleSignatures 32 - blst | 7.8678 ms/op | 7.3835 ms/op | 1.07 |
| BLS verifyMultipleSignatures 64 - blst | 15.055 ms/op | 14.236 ms/op | 1.06 |
| BLS verifyMultipleSignatures 128 - blst | 28.303 ms/op | 26.872 ms/op | 1.05 |
| BLS deserializing 10000 signatures | 669.31 ms/op | 632.30 ms/op | 1.06 |
| BLS deserializing 100000 signatures | 6.6683 s/op | 6.3270 s/op | 1.05 |
| BLS verifyMultipleSignatures - same message - 3 - blst | 985.21 us/op | 947.72 us/op | 1.04 |
| BLS verifyMultipleSignatures - same message - 8 - blst | 1.1199 ms/op | 1.0760 ms/op | 1.04 |
| BLS verifyMultipleSignatures - same message - 32 - blst | 1.7631 ms/op | 1.6931 ms/op | 1.04 |
| BLS verifyMultipleSignatures - same message - 64 - blst | 2.6221 ms/op | 2.5096 ms/op | 1.04 |
| BLS verifyMultipleSignatures - same message - 128 - blst | 4.3473 ms/op | 4.1323 ms/op | 1.05 |
| BLS aggregatePubkeys 32 - blst | 20.037 us/op | 19.404 us/op | 1.03 |
| BLS aggregatePubkeys 128 - blst | 72.587 us/op | 70.530 us/op | 1.03 |
| getSlashingsAndExits - default max | 45.027 us/op | 40.313 us/op | 1.12 |
| getSlashingsAndExits - 2k | 344.96 us/op | 334.86 us/op | 1.03 |
| proposeBlockBody type=full, size=empty | 730.25 us/op | 713.18 us/op | 1.02 |
| isKnown best case - 1 super set check | 176.00 ns/op | 185.00 ns/op | 0.95 |
| isKnown normal case - 2 super set checks | 176.00 ns/op | 167.00 ns/op | 1.05 |
| isKnown worse case - 16 super set checks | 173.00 ns/op | 166.00 ns/op | 1.04 |
| validate api signedAggregateAndProof - struct | 1.5564 ms/op | 1.8182 ms/op | 0.86 |
| validate gossip signedAggregateAndProof - struct | 1.5529 ms/op | 1.7689 ms/op | 0.88 |
| batch validate gossip attestation - vc 640000 - chunk 32 | 125.88 us/op | 124.81 us/op | 1.01 |
| batch validate gossip attestation - vc 640000 - chunk 64 | 108.27 us/op | 106.00 us/op | 1.02 |
| batch validate gossip attestation - vc 640000 - chunk 128 | 97.516 us/op | 95.514 us/op | 1.02 |
| batch validate gossip attestation - vc 640000 - chunk 256 | 97.484 us/op | 98.019 us/op | 0.99 |
| bytes32 toHexString | 300.00 ns/op | 299.00 ns/op | 1.00 |
| bytes32 Buffer.toString(hex) | 190.00 ns/op | 174.00 ns/op | 1.09 |
| bytes32 Buffer.toString(hex) from Uint8Array | 261.00 ns/op | 242.00 ns/op | 1.08 |
| bytes32 Buffer.toString(hex) + 0x | 187.00 ns/op | 172.00 ns/op | 1.09 |
| Return object 10000 times | 0.21840 ns/op | 0.21320 ns/op | 1.02 |
| Throw Error 10000 times | 3.4719 us/op | 3.3113 us/op | 1.05 |
| toHex | 99.027 ns/op | 96.811 ns/op | 1.02 |
| Buffer.from | 91.944 ns/op | 90.173 ns/op | 1.02 |
| shared Buffer | 65.042 ns/op | 63.259 ns/op | 1.03 |
| fastMsgIdFn sha256 / 200 bytes | 1.5270 us/op | 1.4890 us/op | 1.03 |
| fastMsgIdFn h32 xxhash / 200 bytes | 165.00 ns/op | 153.00 ns/op | 1.08 |
| fastMsgIdFn h64 xxhash / 200 bytes | 213.00 ns/op | 207.00 ns/op | 1.03 |
| fastMsgIdFn sha256 / 1000 bytes | 4.8800 us/op | 4.7340 us/op | 1.03 |
| fastMsgIdFn h32 xxhash / 1000 bytes | 254.00 ns/op | 246.00 ns/op | 1.03 |
| fastMsgIdFn h64 xxhash / 1000 bytes | 262.00 ns/op | 258.00 ns/op | 1.02 |
| fastMsgIdFn sha256 / 10000 bytes | 42.826 us/op | 41.784 us/op | 1.02 |
| fastMsgIdFn h32 xxhash / 10000 bytes | 1.2930 us/op | 1.2530 us/op | 1.03 |
| fastMsgIdFn h64 xxhash / 10000 bytes | 829.00 ns/op | 806.00 ns/op | 1.03 |
| send data - 1000 256B messages | 4.0470 ms/op | 3.9887 ms/op | 1.01 |
| send data - 1000 512B messages | 5.3327 ms/op | 4.7722 ms/op | 1.12 |
| send data - 1000 1024B messages | 5.4966 ms/op | 5.2009 ms/op | 1.06 |
| send data - 1000 1200B messages | 6.3035 ms/op | 6.3617 ms/op | 0.99 |
| send data - 1000 2048B messages | 12.592 ms/op | 9.7093 ms/op | 1.30 |
| send data - 1000 4096B messages | 54.297 ms/op | 17.009 ms/op | 3.19 |
| send data - 1000 16384B messages | 385.12 ms/op | 364.64 ms/op | 1.06 |
| send data - 1000 65536B messages | 1.9733 s/op | 1.9527 s/op | 1.01 |
| enrSubnets - fastDeserialize 64 bits | 842.00 ns/op | 745.00 ns/op | 1.13 |
| enrSubnets - ssz BitVector 64 bits | 287.00 ns/op | 268.00 ns/op | 1.07 |
| enrSubnets - fastDeserialize 4 bits | 106.00 ns/op | 101.00 ns/op | 1.05 |
| enrSubnets - ssz BitVector 4 bits | 292.00 ns/op | 266.00 ns/op | 1.10 |
| prioritizePeers score -10:0 att 32-0.1 sync 2-0 | 209.57 us/op | 208.81 us/op | 1.00 |
| prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 | 233.77 us/op | 238.31 us/op | 0.98 |
| prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 | 344.37 us/op | 345.88 us/op | 1.00 |
| prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 | 601.51 us/op | 615.94 us/op | 0.98 |
| prioritizePeers score 0:0 att 64-1 sync 4-1 | 696.21 us/op | 723.46 us/op | 0.96 |
| array of 16000 items push then shift | 1.3346 us/op | 1.3223 us/op | 1.01 |
| LinkedList of 16000 items push then shift | 7.4770 ns/op | 7.3170 ns/op | 1.02 |
| array of 16000 items push then pop | 80.264 ns/op | 80.156 ns/op | 1.00 |
| LinkedList of 16000 items push then pop | 6.2260 ns/op | 6.0370 ns/op | 1.03 |
| array of 24000 items push then shift | 1.9754 us/op | 1.9536 us/op | 1.01 |
| LinkedList of 24000 items push then shift | 7.0450 ns/op | 6.8200 ns/op | 1.03 |
| array of 24000 items push then pop | 116.00 ns/op | 113.69 ns/op | 1.02 |
| LinkedList of 24000 items push then pop | 6.2360 ns/op | 6.0930 ns/op | 1.02 |
| intersect bitArray bitLen 8 | 4.0380 ns/op | 3.8990 ns/op | 1.04 |
| intersect array and set length 8 | 30.528 ns/op | 29.693 ns/op | 1.03 |
| intersect bitArray bitLen 128 | 26.997 ns/op | 23.538 ns/op | 1.15 |
| intersect array and set length 128 | 516.52 ns/op | 502.58 ns/op | 1.03 |
| bitArray.getTrueBitIndexes() bitLen 128 | 976.00 ns/op | 929.00 ns/op | 1.05 |
| bitArray.getTrueBitIndexes() bitLen 248 | 1.7570 us/op | 1.6910 us/op | 1.04 |
| bitArray.getTrueBitIndexes() bitLen 512 | 3.6080 us/op | 3.5240 us/op | 1.02 |
| Full columns - reconstruct all 6 blobs | 136.80 us/op | 189.90 us/op | 0.72 |
| Full columns - reconstruct half of the blobs out of 6 | 76.011 us/op | 69.894 us/op | 1.09 |
| Full columns - reconstruct single blob out of 6 | 33.765 us/op | 32.118 us/op | 1.05 |
| Half columns - reconstruct all 6 blobs | 405.13 ms/op | 402.68 ms/op | 1.01 |
| Half columns - reconstruct half of the blobs out of 6 | 204.17 ms/op | 201.72 ms/op | 1.01 |
| Half columns - reconstruct single blob out of 6 | 72.802 ms/op | 68.663 ms/op | 1.06 |
| Set add up to 64 items then delete first | 2.3433 us/op | 1.6793 us/op | 1.40 |
| OrderedSet add up to 64 items then delete first | 3.7096 us/op | 2.5328 us/op | 1.46 |
| Set add up to 64 items then delete last | 2.4681 us/op | 1.8918 us/op | 1.30 |
| OrderedSet add up to 64 items then delete last | 3.5208 us/op | 2.8024 us/op | 1.26 |
| Set add up to 64 items then delete middle | 2.2388 us/op | 1.8855 us/op | 1.19 |
| OrderedSet add up to 64 items then delete middle | 5.0829 us/op | 4.2658 us/op | 1.19 |
| Set add up to 128 items then delete first | 4.6454 us/op | 3.7985 us/op | 1.22 |
| OrderedSet add up to 128 items then delete first | 7.1790 us/op | 5.7452 us/op | 1.25 |
| Set add up to 128 items then delete last | 4.0117 us/op | 3.6360 us/op | 1.10 |
| OrderedSet add up to 128 items then delete last | 6.0926 us/op | 5.3943 us/op | 1.13 |
| Set add up to 128 items then delete middle | 4.1440 us/op | 3.6444 us/op | 1.14 |
| OrderedSet add up to 128 items then delete middle | 12.347 us/op | 11.437 us/op | 1.08 |
| Set add up to 256 items then delete first | 8.2320 us/op | 7.4896 us/op | 1.10 |
| OrderedSet add up to 256 items then delete first | 12.725 us/op | 11.748 us/op | 1.08 |
| Set add up to 256 items then delete last | 7.9199 us/op | 7.1732 us/op | 1.10 |
| OrderedSet add up to 256 items then delete last | 12.173 us/op | 11.131 us/op | 1.09 |
| Set add up to 256 items then delete middle | 7.9751 us/op | 7.2033 us/op | 1.11 |
| OrderedSet add up to 256 items then delete middle | 38.077 us/op | 34.694 us/op | 1.10 |
| runFastConfirmationRules vc:100000 bc:96 eq:0 | 5.2269 ms/op | 4.5562 ms/op | 1.15 |
| runFastConfirmationRules vc:600000 bc:96 eq:0 | 37.399 ms/op | 34.511 ms/op | 1.08 |
| runFastConfirmationRules vc:1000000 bc:96 eq:0 | 62.794 ms/op | 69.593 ms/op | 0.90 |
| runFastConfirmationRules vc:600000 bc:320 eq:0 | 37.635 ms/op | 34.832 ms/op | 1.08 |
| runFastConfirmationRules vc:100000 bc:96 eq:1000 | 1.2071 s/op | 1.1532 s/op | 1.05 |
| pass gossip attestations to forkchoice per slot | 2.7231 ms/op | 2.5602 ms/op | 1.06 |
| forkChoice updateHead vc 100000 bc 64 eq 0 | 429.31 us/op | 430.22 us/op | 1.00 |
| forkChoice updateHead vc 600000 bc 64 eq 0 | 2.5342 ms/op | 2.5704 ms/op | 0.99 |
| forkChoice updateHead vc 1000000 bc 64 eq 0 | 4.2463 ms/op | 4.2644 ms/op | 1.00 |
| forkChoice updateHead vc 600000 bc 320 eq 0 | 2.5871 ms/op | 2.5814 ms/op | 1.00 |
| forkChoice updateHead vc 600000 bc 1200 eq 0 | 2.6336 ms/op | 2.6134 ms/op | 1.01 |
| forkChoice updateHead vc 600000 bc 7200 eq 0 | 3.2260 ms/op | 3.0593 ms/op | 1.05 |
| forkChoice updateHead vc 600000 bc 64 eq 1000 | 2.5676 ms/op | 2.5751 ms/op | 1.00 |
| forkChoice updateHead vc 600000 bc 64 eq 10000 | 2.6478 ms/op | 2.7033 ms/op | 0.98 |
| forkChoice updateHead vc 600000 bc 64 eq 300000 | 7.3620 ms/op | 7.2562 ms/op | 1.01 |
| forkChoice updateHead vc 600000 bc 64 eq 0 gloas boosted | 2.6486 ms/op | ||
| computeDeltas 1400000 validators 0% inactive | 12.871 ms/op | 12.565 ms/op | 1.02 |
| computeDeltas 1400000 validators 10% inactive | 12.101 ms/op | 11.868 ms/op | 1.02 |
| computeDeltas 1400000 validators 20% inactive | 11.493 ms/op | 11.545 ms/op | 1.00 |
| computeDeltas 1400000 validators 50% inactive | 9.3906 ms/op | 9.0707 ms/op | 1.04 |
| computeDeltas 2100000 validators 0% inactive | 19.316 ms/op | 18.851 ms/op | 1.02 |
| computeDeltas 2100000 validators 10% inactive | 18.218 ms/op | 18.447 ms/op | 0.99 |
| computeDeltas 2100000 validators 20% inactive | 17.146 ms/op | 17.283 ms/op | 0.99 |
| computeDeltas 2100000 validators 50% inactive | 11.440 ms/op | 11.624 ms/op | 0.98 |
| altair processAttestation - 250000 vs - 7PWei normalcase | 1.9139 ms/op | 1.6205 ms/op | 1.18 |
| altair processAttestation - 250000 vs - 7PWei worstcase | 3.0613 ms/op | 2.4577 ms/op | 1.25 |
| altair processAttestation - setStatus - 1/6 committees join | 109.55 us/op | 104.50 us/op | 1.05 |
| altair processAttestation - setStatus - 1/3 committees join | 217.62 us/op | 206.59 us/op | 1.05 |
| altair processAttestation - setStatus - 1/2 committees join | 307.47 us/op | 289.83 us/op | 1.06 |
| altair processAttestation - setStatus - 2/3 committees join | 399.76 us/op | 377.38 us/op | 1.06 |
| altair processAttestation - setStatus - 4/5 committees join | 569.43 us/op | 530.01 us/op | 1.07 |
| altair processAttestation - setStatus - 100% committees join | 657.78 us/op | 624.88 us/op | 1.05 |
| altair processBlock - 250000 vs - 7PWei normalcase | 5.5754 ms/op | 3.8603 ms/op | 1.44 |
| altair processBlock - 250000 vs - 7PWei normalcase hashState | 24.682 ms/op | 20.050 ms/op | 1.23 |
| altair processBlock - 250000 vs - 7PWei worstcase | 28.527 ms/op | 24.041 ms/op | 1.19 |
| altair processBlock - 250000 vs - 7PWei worstcase hashState | 62.753 ms/op | 52.685 ms/op | 1.19 |
| phase0 processBlock - 250000 vs - 7PWei normalcase | 1.4452 ms/op | 1.3069 ms/op | 1.11 |
| phase0 processBlock - 250000 vs - 7PWei worstcase | 22.561 ms/op | 19.802 ms/op | 1.14 |
| altair processEth1Data - 250000 vs - 7PWei normalcase | 315.73 us/op | 303.31 us/op | 1.04 |
| getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 | 3.4200 us/op | 3.2980 us/op | 1.04 |
| getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 | 21.349 us/op | 20.461 us/op | 1.04 |
| getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 | 6.3220 us/op | 5.8870 us/op | 1.07 |
| getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 | 3.9670 us/op | 3.7320 us/op | 1.06 |
| getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 | 95.522 us/op | 90.899 us/op | 1.05 |
| getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 | 1.5502 ms/op | 1.4692 ms/op | 1.06 |
| getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 | 2.0192 ms/op | 1.9070 ms/op | 1.06 |
| getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 | 2.0031 ms/op | 1.9005 ms/op | 1.05 |
| getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 | 4.1751 ms/op | 3.7829 ms/op | 1.10 |
| getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 | 2.2902 ms/op | 2.1368 ms/op | 1.07 |
| getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 | 4.6214 ms/op | 4.1948 ms/op | 1.10 |
| Tree 40 250000 create | 383.91 ms/op | 320.72 ms/op | 1.20 |
| Tree 40 250000 get(125000) | 99.589 ns/op | 102.96 ns/op | 0.97 |
| Tree 40 250000 set(125000) | 1.1045 us/op | 1.0540 us/op | 1.05 |
| Tree 40 250000 toArray() | 19.178 ms/op | 10.984 ms/op | 1.75 |
| Tree 40 250000 iterate all - toArray() + loop | 20.587 ms/op | 11.959 ms/op | 1.72 |
| Tree 40 250000 iterate all - get(i) | 43.578 ms/op | 37.541 ms/op | 1.16 |
| Array 250000 create | 2.8140 ms/op | 2.5500 ms/op | 1.10 |
| Array 250000 clone - spread | 855.21 us/op | 791.67 us/op | 1.08 |
| Array 250000 get(125000) | 0.31800 ns/op | 0.29500 ns/op | 1.08 |
| Array 250000 set(125000) | 0.31900 ns/op | 0.29700 ns/op | 1.07 |
| Array 250000 iterate all - loop | 61.137 us/op | 57.571 us/op | 1.06 |
| phase0 afterProcessEpoch - 250000 vs - 7PWei | 44.084 ms/op | 66.811 ms/op | 0.66 |
| Array.fill - length 1000000 | 4.4018 ms/op | 4.0800 ms/op | 1.08 |
| Array push - length 1000000 | 21.292 ms/op | 15.093 ms/op | 1.41 |
| Array.get | 0.22172 ns/op | 0.20557 ns/op | 1.08 |
| Uint8Array.get | 0.24805 ns/op | 0.27468 ns/op | 0.90 |
| phase0 beforeProcessEpoch - 250000 vs - 7PWei | 21.982 ms/op | 17.236 ms/op | 1.28 |
| altair processEpoch - mainnet_e81889 | 258.67 ms/op | 206.98 ms/op | 1.25 |
| mainnet_e81889 - altair beforeProcessEpoch | 39.784 ms/op | 37.438 ms/op | 1.06 |
| mainnet_e81889 - altair processJustificationAndFinalization | 6.0050 us/op | 4.5790 us/op | 1.31 |
| mainnet_e81889 - altair processInactivityUpdates | 3.8245 ms/op | 3.6330 ms/op | 1.05 |
| mainnet_e81889 - altair processRewardsAndPenalties | 20.386 ms/op | 17.964 ms/op | 1.13 |
| mainnet_e81889 - altair processRegistryUpdates | 628.00 ns/op | 566.00 ns/op | 1.11 |
| mainnet_e81889 - altair processSlashings | 169.00 ns/op | 137.00 ns/op | 1.23 |
| mainnet_e81889 - altair processEth1DataReset | 163.00 ns/op | 134.00 ns/op | 1.22 |
| mainnet_e81889 - altair processEffectiveBalanceUpdates | 1.7740 ms/op | 1.7140 ms/op | 1.04 |
| mainnet_e81889 - altair processSlashingsReset | 771.00 ns/op | 690.00 ns/op | 1.12 |
| mainnet_e81889 - altair processRandaoMixesReset | 1.0390 us/op | 929.00 ns/op | 1.12 |
| mainnet_e81889 - altair processHistoricalRootsUpdate | 171.00 ns/op | 137.00 ns/op | 1.25 |
| mainnet_e81889 - altair processParticipationFlagUpdates | 497.00 ns/op | 444.00 ns/op | 1.12 |
| mainnet_e81889 - altair processSyncCommitteeUpdates | 138.00 ns/op | 108.00 ns/op | 1.28 |
| mainnet_e81889 - altair afterProcessEpoch | 44.951 ms/op | 40.876 ms/op | 1.10 |
| capella processEpoch - mainnet_e217614 | 938.77 ms/op | 669.31 ms/op | 1.40 |
| mainnet_e217614 - capella beforeProcessEpoch | 106.50 ms/op | 57.308 ms/op | 1.86 |
| mainnet_e217614 - capella processJustificationAndFinalization | 5.6710 us/op | 4.8780 us/op | 1.16 |
| mainnet_e217614 - capella processInactivityUpdates | 19.419 ms/op | 13.908 ms/op | 1.40 |
| mainnet_e217614 - capella processRewardsAndPenalties | 99.097 ms/op | 99.616 ms/op | 0.99 |
| mainnet_e217614 - capella processRegistryUpdates | 4.8210 us/op | 4.6400 us/op | 1.04 |
| mainnet_e217614 - capella processSlashings | 170.00 ns/op | 138.00 ns/op | 1.23 |
| mainnet_e217614 - capella processEth1DataReset | 334.00 ns/op | 135.00 ns/op | 2.47 |
| mainnet_e217614 - capella processEffectiveBalanceUpdates | 32.466 ms/op | 10.548 ms/op | 3.08 |
| mainnet_e217614 - capella processSlashingsReset | 1.0850 us/op | 717.00 ns/op | 1.51 |
| mainnet_e217614 - capella processRandaoMixesReset | 2.1900 us/op | 1000.0 ns/op | 2.19 |
| mainnet_e217614 - capella processHistoricalRootsUpdate | 334.00 ns/op | 137.00 ns/op | 2.44 |
| mainnet_e217614 - capella processParticipationFlagUpdates | 1.4250 us/op | 455.00 ns/op | 3.13 |
| mainnet_e217614 - capella afterProcessEpoch | 117.97 ms/op | 107.66 ms/op | 1.10 |
| phase0 processEpoch - mainnet_e58758 | 235.38 ms/op | 207.25 ms/op | 1.14 |
| mainnet_e58758 - phase0 beforeProcessEpoch | 57.271 ms/op | 47.651 ms/op | 1.20 |
| mainnet_e58758 - phase0 processJustificationAndFinalization | 5.4950 us/op | 4.6400 us/op | 1.18 |
| mainnet_e58758 - phase0 processRewardsAndPenalties | 18.374 ms/op | 16.880 ms/op | 1.09 |
| mainnet_e58758 - phase0 processRegistryUpdates | 2.4690 us/op | 2.3230 us/op | 1.06 |
| mainnet_e58758 - phase0 processSlashings | 178.00 ns/op | 136.00 ns/op | 1.31 |
| mainnet_e58758 - phase0 processEth1DataReset | 171.00 ns/op | 289.00 ns/op | 0.59 |
| mainnet_e58758 - phase0 processEffectiveBalanceUpdates | 879.19 us/op | 973.50 us/op | 0.90 |
| mainnet_e58758 - phase0 processSlashingsReset | 1.0250 us/op | 923.00 ns/op | 1.11 |
| mainnet_e58758 - phase0 processRandaoMixesReset | 1.1440 us/op | 1.0050 us/op | 1.14 |
| mainnet_e58758 - phase0 processHistoricalRootsUpdate | 173.00 ns/op | 135.00 ns/op | 1.28 |
| mainnet_e58758 - phase0 processParticipationRecordUpdates | 1.0570 us/op | 929.00 ns/op | 1.14 |
| mainnet_e58758 - phase0 afterProcessEpoch | 36.423 ms/op | 34.726 ms/op | 1.05 |
| phase0 processEffectiveBalanceUpdates - 250000 normalcase | 1.4639 ms/op | 1.0162 ms/op | 1.44 |
| phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 | 1.8640 ms/op | 1.2351 ms/op | 1.51 |
| altair processInactivityUpdates - 250000 normalcase | 12.873 ms/op | 10.976 ms/op | 1.17 |
| altair processInactivityUpdates - 250000 worstcase | 12.858 ms/op | 11.094 ms/op | 1.16 |
| phase0 processRegistryUpdates - 250000 normalcase | 3.0970 us/op | 2.3920 us/op | 1.29 |
| phase0 processRegistryUpdates - 250000 badcase_full_deposits | 187.63 us/op | 142.93 us/op | 1.31 |
| phase0 processRegistryUpdates - 250000 worstcase 0.5 | 82.093 ms/op | 67.018 ms/op | 1.22 |
| altair processRewardsAndPenalties - 250000 normalcase | 19.744 ms/op | 14.935 ms/op | 1.32 |
| altair processRewardsAndPenalties - 250000 worstcase | 22.039 ms/op | 13.911 ms/op | 1.58 |
| phase0 getAttestationDeltas - 250000 normalcase | 6.2185 ms/op | 5.7096 ms/op | 1.09 |
| phase0 getAttestationDeltas - 250000 worstcase | 6.2140 ms/op | 5.7452 ms/op | 1.08 |
| phase0 processSlashings - 250000 worstcase | 70.676 us/op | 61.016 us/op | 1.16 |
| altair processSyncCommitteeUpdates - 250000 | 15.859 ms/op | 10.012 ms/op | 1.58 |
| BeaconState.hashTreeRoot - No change | 309.00 ns/op | 180.00 ns/op | 1.72 |
| BeaconState.hashTreeRoot - 1 full validator | 85.528 us/op | 72.204 us/op | 1.18 |
| BeaconState.hashTreeRoot - 32 full validator | 983.53 us/op | 793.62 us/op | 1.24 |
| BeaconState.hashTreeRoot - 512 full validator | 9.9025 ms/op | 7.2324 ms/op | 1.37 |
| BeaconState.hashTreeRoot - 1 validator.effectiveBalance | 122.09 us/op | 89.418 us/op | 1.37 |
| BeaconState.hashTreeRoot - 32 validator.effectiveBalance | 1.7503 ms/op | 1.1457 ms/op | 1.53 |
| BeaconState.hashTreeRoot - 512 validator.effectiveBalance | 19.846 ms/op | 14.560 ms/op | 1.36 |
| BeaconState.hashTreeRoot - 1 balances | 91.082 us/op | 61.324 us/op | 1.49 |
| BeaconState.hashTreeRoot - 32 balances | 892.73 us/op | 584.43 us/op | 1.53 |
| BeaconState.hashTreeRoot - 512 balances | 7.3834 ms/op | 4.6723 ms/op | 1.58 |
| BeaconState.hashTreeRoot - 250000 balances | 147.16 ms/op | 97.083 ms/op | 1.52 |
| aggregationBits - 2048 els - zipIndexesInBitList | 22.929 us/op | 18.990 us/op | 1.21 |
| regular array get 100000 times | 24.519 us/op | 22.660 us/op | 1.08 |
| wrappedArray get 100000 times | 24.461 us/op | 22.698 us/op | 1.08 |
| arrayWithProxy get 100000 times | 10.001 ms/op | 10.226 ms/op | 0.98 |
| ssz.Root.equals | 22.796 ns/op | 21.406 ns/op | 1.06 |
| byteArrayEquals | 22.596 ns/op | 21.197 ns/op | 1.07 |
| Buffer.compare | 9.3880 ns/op | 8.9070 ns/op | 1.05 |
| processSlot - 1 slots | 10.962 us/op | 8.3220 us/op | 1.32 |
| processSlot - 32 slots | 2.4415 ms/op | 1.6138 ms/op | 1.51 |
| getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei | 5.6371 ms/op | 1.8864 ms/op | 2.99 |
| getCommitteeAssignments - req 1 vs - 250000 vc | 1.7642 ms/op | 1.6663 ms/op | 1.06 |
| getCommitteeAssignments - req 100 vs - 250000 vc | 3.6217 ms/op | 3.4240 ms/op | 1.06 |
| getCommitteeAssignments - req 1000 vs - 250000 vc | 3.8965 ms/op | 3.6992 ms/op | 1.05 |
| findModifiedValidators - 10000 modified validators | 1.0632 s/op | 807.52 ms/op | 1.32 |
| findModifiedValidators - 1000 modified validators | 794.64 ms/op | 537.24 ms/op | 1.48 |
| findModifiedValidators - 100 modified validators | 422.81 ms/op | 365.89 ms/op | 1.16 |
| findModifiedValidators - 10 modified validators | 345.88 ms/op | 308.92 ms/op | 1.12 |
| findModifiedValidators - 1 modified validators | 314.29 ms/op | 269.42 ms/op | 1.17 |
| findModifiedValidators - no difference | 235.46 ms/op | 232.37 ms/op | 1.01 |
| migrate state 1500000 validators, 3400 modified, 2000 new | 3.8355 s/op | 2.9430 s/op | 1.30 |
| RootCache.getBlockRootAtSlot - 250000 vs - 7PWei | 3.8800 ns/op | 3.6500 ns/op | 1.06 |
| state getBlockRootAtSlot - 250000 vs - 7PWei | 432.50 ns/op | 297.06 ns/op | 1.46 |
| computeProposerIndex 100000 validators | 1.4397 ms/op | 1.3635 ms/op | 1.06 |
| getNextSyncCommitteeIndices 1000 validators | 3.0522 ms/op | 2.8753 ms/op | 1.06 |
| getNextSyncCommitteeIndices 10000 validators | 26.713 ms/op | 25.899 ms/op | 1.03 |
| getNextSyncCommitteeIndices 100000 validators | 94.949 ms/op | 87.450 ms/op | 1.09 |
| computeProposers - vc 250000 | 573.50 us/op | 543.06 us/op | 1.06 |
| computeEpochShuffling - vc 250000 | 42.167 ms/op | 38.432 ms/op | 1.10 |
| getNextSyncCommittee - vc 250000 | 10.115 ms/op | 9.3943 ms/op | 1.08 |
| nodejs block root to RootHex using toHex | 103.33 ns/op | 99.744 ns/op | 1.04 |
| nodejs block root to RootHex using toRootHex | 65.139 ns/op | 66.086 ns/op | 0.99 |
| nodejs fromHex(blob) | 916.54 us/op | 1.0321 ms/op | 0.89 |
| nodejs fromHexInto(blob) | 662.60 us/op | 647.82 us/op | 1.02 |
| nodejs block root to RootHex using the deprecated toHexString | 626.36 ns/op | 606.67 ns/op | 1.03 |
| nodejs byteArrayEquals 32 bytes (block root) | 27.378 ns/op | 26.567 ns/op | 1.03 |
| nodejs byteArrayEquals 48 bytes (pubkey) | 39.566 ns/op | 38.179 ns/op | 1.04 |
| nodejs byteArrayEquals 96 bytes (signature) | 36.577 ns/op | 37.214 ns/op | 0.98 |
| nodejs byteArrayEquals 1024 bytes | 44.872 ns/op | 45.135 ns/op | 0.99 |
| nodejs byteArrayEquals 131072 bytes (blob) | 1.8717 us/op | 1.8201 us/op | 1.03 |
| browser block root to RootHex using toHex | 151.88 ns/op | 147.80 ns/op | 1.03 |
| browser block root to RootHex using toRootHex | 137.59 ns/op | 130.45 ns/op | 1.05 |
| browser fromHex(blob) | 1.9337 ms/op | 1.7267 ms/op | 1.12 |
| browser fromHexInto(blob) | 659.07 us/op | 659.58 us/op | 1.00 |
| browser block root to RootHex using the deprecated toHexString | 448.36 ns/op | 387.08 ns/op | 1.16 |
| browser byteArrayEquals 32 bytes (block root) | 29.566 ns/op | 28.016 ns/op | 1.06 |
| browser byteArrayEquals 48 bytes (pubkey) | 41.696 ns/op | 39.513 ns/op | 1.06 |
| browser byteArrayEquals 96 bytes (signature) | 78.512 ns/op | 74.013 ns/op | 1.06 |
| browser byteArrayEquals 1024 bytes | 797.14 ns/op | 749.04 ns/op | 1.06 |
| browser byteArrayEquals 131072 bytes (blob) | 99.863 us/op | 94.366 us/op | 1.06 |
by benchmarkbot/action
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into the existing `forkChoiceTest` runner. The on-disk layout matches the standard spec-test layout (`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`), so it slots in alongside `fork_choice` and `sync` runners. Three test-only accommodations the compliance fixtures require: 1. `bls_setting: 2` — every compliance fixture uses placeholder signatures. Pass `validSignatures: testcase.meta?.bls_setting !== BigInt(1)` to `chain.processBlock` so verification short-circuits. Standard `fork_choice` fixtures use `bls_setting: 1` so behavior there is unchanged. 2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally re-import the same block (`dup_shift` mutations in their `meta.yaml`). Spec semantics for `on_block(store, known_block)` is a no-op success. Production block import correctly rejects with ALREADY_KNOWN; this runner treats that case as success only when the step is `valid: true`. 3. Cross-epoch attestation shuffling — `on_attestation` decodes aggregation_bits using the state at the attestation's target checkpoint, not the head state. The runner now resolves the right shuffling via ShufflingCache + regen (mirroring the production validation path) instead of `headState.epochCtx.getIndexedAttestation`, which only worked when the attestation's epoch happened to be in the head's epoch cache (±1 epoch) and broke on cross-epoch fork attestations surfaced by the compliance suite. Adds support for two compliance-only check fields: - `viable_for_head_roots_and_weights` (consensus-specs#3831): compared via `getViableHeads()`. Both sides are sorted by root before comparison since the spec doesn't fix order. - `head_payload_status` (gloas): mapped between our internal enum ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0, FULL=1, PENDING=2). Pass rate against the latest comptests workflow `small.tar.gz` artifact: fulu/fork_choice_compliance: 253/1472 cases pass (17.2%) Top remaining failures: - ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced a `block.proposer_index == get_beacon_proposer_index(head_state)` guard in `update_proposer_boost_root` that we do not yet implement; affects all forks (not just gloas equivocation handling). Tracked for follow-up alongside ChainSafe#9233. - ~1% `Invalid viable heads` — proposer-boost rounding on minimal preset (see `getViableHeads()` weight note).
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into the existing `forkChoiceTest` runner. The on-disk layout matches the standard spec-test layout (`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`), so it slots in alongside `fork_choice` and `sync` runners. Three test-only accommodations the compliance fixtures require: 1. `bls_setting: 2` — every compliance fixture uses placeholder signatures. Pass `validSignatures: testcase.meta?.bls_setting !== BigInt(1)` to `chain.processBlock` so verification short-circuits. Standard `fork_choice` fixtures use `bls_setting: 1` so behavior there is unchanged. 2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally re-import the same block (`dup_shift` mutations in their `meta.yaml`). Spec semantics for `on_block(store, known_block)` is a no-op success. Production block import correctly rejects with ALREADY_KNOWN; this runner treats that case as success only when the step is `valid: true`. 3. Cross-epoch attestation shuffling — `on_attestation` decodes aggregation_bits using the state at the attestation's target checkpoint, not the head state. The runner now resolves the right shuffling via ShufflingCache + regen (mirroring the production validation path) instead of `headState.epochCtx.getIndexedAttestation`, which only worked when the attestation's epoch happened to be in the head's epoch cache (±1 epoch) and broke on cross-epoch fork attestations surfaced by the compliance suite. Adds support for two compliance-only check fields: - `viable_for_head_roots_and_weights` (consensus-specs#3831): compared via `getViableHeads()`. Both sides are sorted by root before comparison since the spec doesn't fix order. - `head_payload_status` (gloas): mapped between our internal enum ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0, FULL=1, PENDING=2). Pass rate against the latest comptests workflow `small.tar.gz` artifact: fulu/fork_choice_compliance: 253/1472 cases pass (17.2%) Top remaining failures: - ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced a `block.proposer_index == get_beacon_proposer_index(head_state)` guard in `update_proposer_boost_root` that we do not yet implement; affects all forks (not just gloas equivocation handling). Tracked for follow-up alongside ChainSafe#9233. - ~1% `Invalid viable heads` — proposer-boost rounding on minimal preset (see `getViableHeads()` weight note).
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into the existing `forkChoiceTest` runner. The on-disk layout matches the standard spec-test layout (`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`), so it slots in alongside `fork_choice` and `sync` runners. Three test-only accommodations the compliance fixtures require: 1. `bls_setting: 2` — every compliance fixture uses placeholder signatures. Pass `validSignatures: testcase.meta?.bls_setting !== BigInt(1)` to `chain.processBlock` so verification short-circuits. Standard `fork_choice` fixtures use `bls_setting: 1` so behavior there is unchanged. 2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally re-import the same block (`dup_shift` mutations in their `meta.yaml`). Spec semantics for `on_block(store, known_block)` is a no-op success. Production block import correctly rejects with ALREADY_KNOWN; this runner treats that case as success only when the step is `valid: true`. 3. Cross-epoch attestation shuffling — `on_attestation` decodes aggregation_bits using the state at the attestation's target checkpoint, not the head state. The runner now resolves the right shuffling via ShufflingCache + regen (mirroring the production validation path) instead of `headState.epochCtx.getIndexedAttestation`, which only worked when the attestation's epoch happened to be in the head's epoch cache (±1 epoch) and broke on cross-epoch fork attestations surfaced by the compliance suite. Adds support for two compliance-only check fields: - `viable_for_head_roots_and_weights` (consensus-specs#3831): compared via `getViableHeads()`. Both sides are sorted by root before comparison since the spec doesn't fix order. - `head_payload_status` (gloas): mapped between our internal enum ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0, FULL=1, PENDING=2). Pass rate against the latest comptests workflow `small.tar.gz` artifact: fulu/fork_choice_compliance: 253/1472 cases pass (17.2%) Top remaining failures: - ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced a `block.proposer_index == get_beacon_proposer_index(head_state)` guard in `update_proposer_boost_root` that we do not yet implement; affects all forks (not just gloas equivocation handling). Tracked for follow-up alongside ChainSafe#9233. - ~1% `Invalid viable heads` — proposer-boost rounding on minimal preset (see `getViableHeads()` weight note).
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into the existing `forkChoiceTest` runner. The on-disk layout matches the standard spec-test layout (`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`), so it slots in alongside `fork_choice` and `sync` runners. Three test-only accommodations the compliance fixtures require: 1. `bls_setting: 2` — every compliance fixture uses placeholder signatures. Pass `validSignatures: testcase.meta?.bls_setting !== BigInt(1)` to `chain.processBlock` so verification short-circuits. Standard `fork_choice` fixtures use `bls_setting: 1` so behavior there is unchanged. 2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally re-import the same block (`dup_shift` mutations in their `meta.yaml`). Spec semantics for `on_block(store, known_block)` is a no-op success. Production block import correctly rejects with ALREADY_KNOWN; this runner treats that case as success only when the step is `valid: true`. 3. Cross-epoch attestation shuffling — `on_attestation` decodes aggregation_bits using the state at the attestation's target checkpoint, not the head state. The runner now resolves the right shuffling via ShufflingCache + regen (mirroring the production validation path) instead of `headState.epochCtx.getIndexedAttestation`, which only worked when the attestation's epoch happened to be in the head's epoch cache (±1 epoch) and broke on cross-epoch fork attestations surfaced by the compliance suite. Adds support for two compliance-only check fields: - `viable_for_head_roots_and_weights` (consensus-specs#3831): compared via `getViableHeads()`. Both sides are sorted by root before comparison since the spec doesn't fix order. - `head_payload_status` (gloas): mapped between our internal enum ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0, FULL=1, PENDING=2). Pass rate against the latest comptests workflow `small.tar.gz` artifact: fulu/fork_choice_compliance: 253/1472 cases pass (17.2%) Top remaining failures: - ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced a `block.proposer_index == get_beacon_proposer_index(head_state)` guard in `update_proposer_boost_root` that we do not yet implement; affects all forks (not just gloas equivocation handling). Tracked for follow-up alongside ChainSafe#9233. - ~1% `Invalid viable heads` — proposer-boost rounding on minimal preset (see `getViableHeads()` weight note).
|
@ensi321 is this still relevant? |
Hardcoding PayloadStatus.PENDING in the weak-parent lookup crashes at the gloas fork transition: the first gloas block's parent is pre-gloas (FULL-only), and getNodeIndexByRootAndStatus throws INVALID_NODE_INDEX when PENDING is requested for a pre-gloas root. The adjacent-parent guard does not short-circuit this path at the boundary. Use parentBlock.payloadStatus instead, matching the pattern already used for the strong-variant lookup elsewhere in this file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
For some reason, our unstable is now passing these tests even without this PR and we failed them previously lodestar/packages/beacon-node/test/spec/presets/fork_choice.test.ts Lines 738 to 742 in e6d38ec But we still need to implement Added some new tests ethereum/consensus-specs#5441 to make sure this is covered. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## unstable #9233 +/- ##
=========================================
Coverage 52.49% 52.49%
=========================================
Files 848 848
Lines 60487 60486 -1
Branches 4466 4465 -1
=========================================
Hits 31755 31755
Misses 28670 28670
+ Partials 62 61 -1 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2bb9684f2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| // Indicate whether block arrives in a timely manner ie. before the 4 second mark | ||
| // Spec: Store.block_timeliness[ATTESTATION_TIMELINESS_INDEX] | ||
| timeliness: boolean; |
There was a problem hiding this comment.
we may want to use a single block_timeliness: number to represent these 2 flags, cc @wemeetagain to confirm
There was a problem hiding this comment.
Is there a real benefit to this? Using bits impact readability, and we are only saving one boolean field per ProtoNode
**Motivation** - is_head_weak() is needed for should_apply_proposer_boost() #9233 - we also need it for `getProposerHead()` **Description** - implement cross-fork [is_head_weak()](https://github.com/ethereum/consensus-specs/blob/6408b990c0b4e58d4f50ae49b59384e82e5e06d8/specs/gloas/fork-choice.md?plain=1#L739) spec api and use it in `getProposerHead()` - track attestation weight in ProtoNode separately, weight still means total weight - back propagate boost deltas and attestation deltas separately - also implement is_parent_strong() **AI Assistance Disclosure** - created with the help of Claude --------- Co-authored-by: Tuyen Nguyen <twoeths@users.noreply.github.com> Co-authored-by: bing <spiralladder@fastmail.com>
…roposer-boost # Conflicts: # packages/beacon-node/test/spec/presets/fork_choice.test.ts
…ProposerBoost is_head_weak() landed via #9654 with the exact gloas semantics (boost-excluded attestation score + committee-scoped equivocator balance add-back), so the weak-parent check can call it directly instead of approximating with raw node.weight against the reorg threshold. Also adapt findEquivocatingBlocks to the current VariantIndices layout via getDefaultNodeIndex, and add the new ProtoBlock fields to test fixtures that were added on unstable since the last merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
should_apply_proposer_boost judges the parent via is_head_weak against the attestations known to the store, but the decision ran before applyScoreChanges() landed this round's deltas, so it read the previous round's attestation scores. A parent crossing the weak threshold on the pending batch kept the boost withheld, and a newly detected equivocator kept its discounted vote counted. For a gloas boosted block, split the score update in two passes: attestation deltas first, then the boost decision, then the boost deltas. Pre-gloas keeps the single pass since the boost is unconditional there. Also replace findEquivocatingBlocks() with hasEquivocatingBlock(): the only caller tests emptiness, so return on first match instead of allocating a list. The two ordering tests fail on the previous commit and pass on this one; the spec vectors cannot catch this because the check step recomputes the head, self-healing the transient decision before assertions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measures the two-pass applyScoreChanges overhead against the single-pass pre-gloas row: 2.96 ms/op vs 2.72 ms/op at vc 600k bc 64 (~9%), paid only while a gloas block holds proposer boost. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| * | ||
| * Child class can overwrite this for testing purpose. | ||
| */ | ||
| protected isBlockPtcTimely(block: BeaconBlock, blockDelaySec: number): boolean { |
There was a problem hiding this comment.
so this should only be relevant in case of proposer equivocation to make sure the next proposer can see it and can be aware of the equivocation in time?
so using the ptc timing seems pretty arbitrary to me but the purpose if I understand this correctly is to figure out if the it was an "early equivocation"
should this be renamed?
| protected isBlockPtcTimely(block: BeaconBlock, blockDelaySec: number): boolean { | |
| protected isBlockEquivocationTimely(block: BeaconBlock, blockDelaySec: number): boolean { |
Edit: after thinking more about it, not sure the rename is better because it could suggest that the block is an equivocation
There was a problem hiding this comment.
Yea I like isBlockPtcTimely
| continue; | ||
| } | ||
| const node = this.nodes[nodeIndex]; | ||
| if (node !== undefined && node.slot === slot && node.proposerIndex === proposerIndex && node.ptcTimeliness) { |
There was a problem hiding this comment.
I am not sure this can happen right now, if we receive a equivocating block over gossip, we would just ignore it
lodestar/packages/beacon-node/src/chain/validation/block.ts
Lines 86 to 90 in 148a1c5
this is kinda related to #9757, need to see if we wanna use seenBlockProposers or fork choice for this, but generally, the equivocation check on the api shouldn't care about timings, if there was an equivocation when submitting the payload, then it's safe to withhold as you don't need to pay the bid.value to the proposer
There was a problem hiding this comment.
the IGNORE is more about the gossipsub spec, we should still process it and add to forkchoice #9805
some peers may still vote for the 2nd block, we'll use UnknownBlockInput sync to download, process and add to forkchoice soon anyway
| this.protoArray.applyScoreChanges({attestationDeltas, proposerBoost: null, ...checkpoints}); | ||
| const proposerBoost = this.shouldApplyProposerBoost() ? this.getProposerBoost() : null; | ||
| this.protoArray.applyScoreChanges({ |
There was a problem hiding this comment.
is calling applyScoreChanges twice fine? cc @twoeths
There was a problem hiding this comment.
right now applyScoreChanges() does 2 things:
- (1) update node's weights
- (2) update best child/descendants
we can improve by calling 1) twice and call 2) in the end
it's more like a refactor and does not affect fulu so we can just leave a TODO and address in another PR I guess
|
Going to reflect the changes from ethereum/consensus-specs#5515 also |
The alpha.14 comptests were regenerated, so the seed-embedding case names changed again. Full re-run and re-triage of the gloas suite: - #9821 (exact Gwei weights) fixed the proposer boost weight quantization divergence (#9694); that skip bucket is removed. - #9864 (recompute head after pulling up checkpoints) fixed the stale cached head read in the proposer boost dependent-root gate (#9666); both remaining pins are removed. - 127 cases remain skipped for consensus-specs#5496 (childless payload-status variants bypass the filter_block_tree FFG check), regenerated names. This is the only remaining failure bucket. Suite result with #9233 merged locally: 1345 passed, 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Vw1BUX36pwcv52fGr3n1C
Summary
Implements the
should_apply_proposer_boostlogic ethereum/consensus-specs/pull/4807 (gloas/fork-choice.md#new-should_apply_proposer_boost).Was blocked on
is_head_weak(); now rebased on top of #9654 (merged) and reuses itsisHeadWeak()— boost-excluded attestation score + equivocator balance add-back — for the weak-parent check, exactly matching the spec'sis_head_weak(store, parent_root)call.Changes
ptcTimelinessandproposerIndexfields toProtoBlockisBlockPtcTimelyto track PTC deadline timelinessshouldApplyProposerBoostwhich withholds boost when the parent is a weak, equivocating block from the previous slotisHeadWeak()from feat: implement forkchoice is_head_weak() and is_parent_strong() #9654 for the weak-parent checkfindEquivocatingBlocksinProtoArrayto detect proposer equivocationsgetWeightonshouldApplyProposerBoost()Note on test coverage
The existing gloas fork_choice vectors pass with or without this gate (the withhold branch executes but never decides the asserted head), so CI alone doesn't discriminate it. Discriminating vectors are proposed upstream in consensus-specs (
test_should_apply_proposer_boost.py, MC/DC over the three apply conditions); the withheld case fails onunstableand passes on this branch.AI Assistance Disclosure
Created with the help of Claude.
🤖 Generated with Claude Code